/**
 * 
 */
package com.novartis.nims.trialsubmitter;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;

import com.pixelmed.dicom.Attribute;
import com.pixelmed.dicom.AttributeList;
import com.pixelmed.dicom.AttributeTag;
import com.pixelmed.dicom.DicomException;
import com.pixelmed.dicom.DicomInputStream;
import com.pixelmed.dicom.TagFromName;

/**
 * @author Josh Snyder
 *
 */
class UnsortedTrialReader extends TrialReader {
	
	private Map<String, Integer> mScanRepeatKeyUsedForFileScanKey;
	private Map<String, Integer> mNextRepeatKeyForScanKey;
	private Map<String, String> mFileKeyNameMap;

	/**
	 * @param ts
	 * @param resource
	 */
	UnsortedTrialReader(TrialSubmitter ts, URI trialLocation) {
		super(ts, trialLocation);
	}

	/* (non-Javadoc)
	 * @see com.novartis.nims.trialsubmitter.TrialReader#initialize()
	 */
	@Override
	protected void initialize() {
		mScanRepeatKeyUsedForFileScanKey = new HashMap<String, Integer>();
		mNextRepeatKeyForScanKey = new HashMap<String, Integer>();
		mFileKeyNameMap = new HashMap<String, String>();
	}

	/* (non-Javadoc)
	 * @see com.novartis.nims.trialsubmitter.TrialReader#processFile(java.io.File)
	 */
	@Override
	protected SubmissionKey processFile(File file, URI relativeLocation) throws TrialReaderException {
		String trialKey = null;
		String subjectKey = null;
		String visitKey = null;
		String visitNumber = null;
		String visitRepeatKey = null;
		String scanKey = null;
		String scanRepeatKey = null;
		
		AttributeList attributes = readDICOMAttributes(file);
		if (attributes == null) {
			abortOrWarn(getAbortOnUnspecifiedFileError(), file.getAbsolutePath() +
				" couldn't be opened as a DICOM file.");
			return null;
		}
		
		String[] components = relativeLocation.getPath().split("/");
		int numDirectories = components.length - 1; //remove trailing file component from directory count
		int trialIndex = 0;
		int subjectIndex = trialIndex + 1;
		if (numDirectories < subjectIndex + 1) {
			throw new TrialReaderException(file.getAbsolutePath() + " does not have trial and subject parent directories. " +
					"Try re-organizing the input files and running again.");
		}
			
		trialKey = components[trialIndex];		
		subjectKey = components[subjectIndex];
		int visitIndex = subjectIndex + 1;
		int scanIndex = visitIndex + 1;
		
		// determine visit key and visit repeat key
		if (getVisitsOrganized()) {			
			if (numDirectories < visitIndex + 1) {
				throw new TrialReaderException(file.getAbsolutePath() + " does not have trial, subject, and visit parent directories. " +
						"Try re-organizing the input files and running again.");
			}
			
			scanIndex = visitIndex + 1;
			
			StringBuilder parsedVisitName = new StringBuilder();
			StringBuilder parsedVisitNumber = new StringBuilder();
			StringBuilder parsedRepeatVisitNumber = new StringBuilder();
			if (! this.parseVisitString(components[visitIndex], parsedVisitName, parsedVisitNumber, parsedRepeatVisitNumber)) {
				throw new TrialReaderException(file.getAbsolutePath() + " does not have a valid visit directory name. " +
						"Try making sure the visit name, number and/or repeat number are present and separated " +
						"correctly.");
			}
			visitKey = parsedVisitName.toString();
			visitNumber = parsedVisitNumber.toString();
			visitRepeatKey = parsedRepeatVisitNumber.toString().isEmpty() ? "1" : parsedRepeatVisitNumber.toString();

		} else {
			String fileVisitKey = getAttributeValue(attributes, getVisitKey());
			if (fileVisitKey == null) {
				abortOrWarn(getAbortOnFileMissingVisitKey(), file.getAbsolutePath() +
						" missing DICOM attribute " + getVisitKey().toString() + ".");
				return null;
			} else if (fileVisitKey.isEmpty()) {
				abortOrWarn(getAbortOnFileMissingVisitKey(), file.getAbsolutePath() +
						" has empty DICOM attribute " + getVisitKey().toString() + ".");
				return null;
			}
			
			// since the same key value is used to identify a file's visit in both
			// our dicom model and our submission model, the repeat key doesn't
			// need to vary.
			visitKey = fileVisitKey;
			visitNumber = "";
			visitRepeatKey = "1";
		}

		
		// determine scan key
		if (getScansOrganized()) {
			if (numDirectories < scanIndex + 1) {
				throw new TrialReaderException(file.getAbsolutePath() + " does not have trial, subject, visit, and scan parent directories. " +
						"Try re-organizing the input files and running again.");
			}
			
			scanKey = components[scanIndex];
		} else {
			String fileScanName = getAttributeValue(attributes, getScanName());
			if (fileScanName == null) {
				abortOrWarn(getAbortOnFileMissingScanName(), file.getAbsolutePath() +
						" missing DICOM attribute " + getScanName().toString() + ".");
				return null;
			} else if (fileScanName.isEmpty()) {
				abortOrWarn(getAbortOnFileMissingScanName(), file.getAbsolutePath() +
						" has empty DICOM attribute " + getScanName().toString() + ".");
				return null;
			}
			scanKey = fileScanName;
		}
		
		
		// determine scan repeat key			
		String fileScanKey = getAttributeValue(attributes, getScanKey());
		if (fileScanKey == null) {
			abortOrWarn(getAbortOnFileMissingScanKey(), file.getAbsolutePath() +
					" missing DICOM attribute " + getScanKey().toString() + ".");
			return null;
		} else if (fileScanKey.isEmpty()) {
			abortOrWarn(getAbortOnFileMissingScanKey(), file.getAbsolutePath() +
					" has empty DICOM attribute " + getScanKey().toString() + ".");
			return null;
		}
		
		String baseCompositeKey = subjectKey+visitKey+visitRepeatKey;
		String scanCompositeKey = baseCompositeKey+scanKey;
		String fileScanCompositeKey = baseCompositeKey+fileScanKey;
		Integer scanRepeatNum = mScanRepeatKeyUsedForFileScanKey.get(fileScanCompositeKey);
		if (scanRepeatNum == null) {
			scanRepeatNum = mNextRepeatKeyForScanKey.get(scanCompositeKey);
			if (scanRepeatNum == null) scanRepeatNum = new Integer(1);
			mScanRepeatKeyUsedForFileScanKey.put(fileScanCompositeKey, scanRepeatNum);
			mNextRepeatKeyForScanKey.put(scanCompositeKey, new Integer(scanRepeatNum + 1));
		}

		scanRepeatKey = String.valueOf(scanRepeatNum);
		

		
		//determine file key
		String fileKey = getAttributeValue(attributes, getFileKey());
		if (fileKey == null) {
			abortOrWarn(getAbortOnFileMissingFileKey(), file.getAbsolutePath() +
					" missing DICOM attribute " + getFileKey().toString() + ".");
			return null;
		} else if (fileKey.isEmpty()) {
			abortOrWarn(getAbortOnFileMissingFileKey(), file.getAbsolutePath() +
					" has empty DICOM attribute " + getFileKey().toString() + ".");
			return null;
		}
		

		String previousFileName = mFileKeyNameMap.put(fileKey, file.getAbsolutePath());
		if (previousFileName != null) {
			abortOrWarn(getAbortOnFileWithDuplicateFileKey(), file.getAbsolutePath() +
					" contains duplicate SOP Instance UID " + fileKey +	" already found in file " +
					previousFileName);
			return null;
		}
		
		
		
		SubmissionKey fullKey = new SubmissionKey(
				trialKey,
				subjectKey,
				visitKey,
				visitRepeatKey,
				scanKey,
				scanRepeatKey);
		
		fullKey.setVisitNumber(visitNumber);
		
		return fullKey;
	}

	private AttributeList readDICOMAttributes(File dicomFile) {
		AttributeList list = new AttributeList();
		DicomInputStream dis = null;
		try {
			dis = new DicomInputStream(dicomFile);
			try {
				list.read(dis);
			} catch (DicomException e) {
				if (! this.getAllowBadPixelData()) return null;
				
				/* exception may be due to truncated pixel data. try stopping the read w/out
				 * processing pixel data or later elements. there are no elements of interest
				 * after pixel data, so this is safe. we processed it in the first place, so
				 * that we can detect this case and issue a warning.
				 */
				
				mLogger.warn(dicomFile.getAbsolutePath() + " may be truncated. Ignoring pixel data and re-trying read.");
				try {
					list.read(dis, TagFromName.PixelData);
				} catch (DicomException de) {
					return null;
				}
			} finally {
				if (dis != null) dis.close();
			}
		} catch (IOException e) {
			return null;
		}
		
		return list;
	}
	
	/**
	 * Extract the value of a particular attribute from a list of DICOM attributes.
	 * 
	 * @param list - the list of DICOM attributes to parse
	 * @param tag - the tag describing which attribute to extract
	 * @return null if the attribute wasn't found. an empty string if the attribute
	 * was found but contained no value. otherwise, the string value of the attribute
	 */
	private String getAttributeValue(AttributeList list, AttributeTag tag) {
		Attribute attr = list.get(tag);
		String value = null;
		if (attr != null) {		
			value = attr.getSingleStringValueOrNull();
		}
		
		return attr == null ? null : (value == null ? "" : value);
	}
	
	private void abortOrWarn(boolean abort, String message) throws TrialReaderException {
		if (abort) {
			throw new TrialReaderException(message);
		} else {
			mLogger.warn(message);
		}
	}

}
